/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-04
 * V4.0
 */
package com.jphenix.driver.log.xlogc;

import com.jphenix.driver.log.executetimevoa.ExecuteTimeVO;
import com.jphenix.driver.log.util.LogAlertCode;
import com.jphenix.driver.log.util.LogUtil;
import com.jphenix.driver.threadpool.ThreadSession;
import com.jphenix.share.lang.SBoolean;
import com.jphenix.share.lang.SString;
import com.jphenix.share.util.DebugUtil;
import com.jphenix.standard.beans.IShadow;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.log.ILog;
import com.jphenix.standard.log.ILogShadow;
import com.jphenix.standard.servlet.IServletConst;

import java.util.List;
import java.util.Map;

/**
 * 文件日志类
 * 
 * 输出日志增加了同步关键字
 * 
 * 2018-07-12 增加了输出内部调试日志方法
 * 2018-07-31 故障码，细化为前台错误和后台错误
 * 2018-09-13 简化了整个架构中日志初始化步骤
 * 2018-12-14 增加了输出会话主键信息，用来按照会话筛选日志
 * 2018-12-21 修改了，显示线程ID值，而不是原来的线程的HashCode
 *            修改了显示线程id错误值的问题
 * 2019-02-01 统一是否输出日志开关
 * 2019-03-06 增加了输出sql语句的方法（带是否输出日志，是否写日志参数）
 *            修改了一下输出sql语句，是否输出控制逻辑
 * 2019-03-15 增加了sql语句输出到自定义日志文件
 * 2019-08-02 提高效率，除了tlog方法，其它方法不解析输出日志参数对象
 * 2019-08-24 解决了debug日志输出到控制台的错误。这个类型的日志不输出到控制台
 * 2020-07-21 还原了版本，之前将一条日志变成一行，但很不实用，还原成老样子
 * 2020-08-26 将入参Exception换成了Throwable
 * 2021-08-02 增加了解析输出数组值对象
 * 2021-09-01 解决了调用日志方法传入空对象时报错的问题
 * 2021-09-11 增加了日志提示标题入参
 * 
 * @author 刘虻
 * 2009-12-4 下午05:01:47
 */
@ClassInfo({"2021-09-11 16:42","文件日志类"})
public class XLog implements ILog,IShadow {

	protected ILogShadow shadow;                  //日志影子类
	protected String     logBossClassName = null; //调用日志类
	
	/**
	 * 构造函数
	 * @author 刘虻
	 */
	public XLog(ILogShadow shadow) {
		super();
		this.shadow = shadow;
	}

	
	/**
	 * 处理日志内容
	 * 刘虻
	 * 2009-12-4 上午11:30:07
	 * @param head 日志头
	 * @param content 未处理的日志内容
	 * @param e 异常，或者放入调用类用来输出堆栈信息
	 * @return 处理后的日志内容
	 */
	@SuppressWarnings("rawtypes")
	public String fixContent(String head,String title,Object content,Object e,boolean needParseContent) {
        //构建返回值
        StringBuffer reSbf = new StringBuffer();
        reSbf
            .append("\n[")
            .append(head)
            .append("] t:[")
            .append(shadow.now())
            .append("] b:[")
            .append(getBossName())
            .append("] h:[")
            .append(Thread.currentThread().getId())
			.append("] s:[")
			.append(SString.valueOf(ThreadSession.get(IServletConst.KEY_SESSION_ID)))
			.append("] p:[")
			.append(SString.valueOf(ThreadSession.get(IServletConst.KEY_ACTION_IP)))
			.append("] a:[")
			.append(SString.valueOf(ThreadSession.get(IServletConst.KEY_ACTION_NAME)))
			.append("]");
        
        //获取调用类信息
        //注意：只是显示调用的action的脚本编号
        //如果每调用个脚本，就会出现调用的脚本编号覆盖了调用的action脚本编号
        //这样就又无法定位具体问题调用的哪个action了
        Object value = ThreadSession.get("_action_ invoker_"); 
        if(value!=null) {
            reSbf.append(" i:[").append(value).append("]");
        }
        //从线程会话中获取值
        value = ThreadSession.get("_action_");  //动作路径
        if(value!=null) {
            reSbf.append(" a:[").append(value).append("]");
        }
        value = ThreadSession.get("_session_key_");  //会话主键
        if(value!=null) {
            reSbf.append(" s:[").append(value).append("]");
        }
        if(title==null){
          title = "";
        }else{
          title += ":";
        }
        if (needParseContent) {
          if(content==null){
            reSbf.append("\n***c:[").append(title).append("NULL]***");
          } else if (content instanceof Throwable) {
            reSbf.append("\n***c:[").append(title).append(DebugUtil.getExceptionInfo((Throwable) content)).append("]***");
          } else if (content instanceof Map) {
            reSbf.append("\n***c:[").append(title).append("Map:\n").append(DebugUtil.getMapValue((Map) content)).append("]***");
          } else if (content instanceof List) {
            reSbf.append("\n***c:[").append(title).append("List:\n").append(DebugUtil.getListValue((List) content)).append("]***");
          } else if (content.getClass().isArray()) {
            reSbf.append("\n***c:[").append(title).append("List:\n").append(DebugUtil.getArrayString((Object[]) content)).append("]***");
          } else {
            reSbf.append("\n***c:[").append(title).append(content).append("]***");
          }
        } else {
          reSbf.append("\n***c:[").append(title).append(content).append("]***");
        }
        if (e != null) {
          reSbf.append("\n***e:[");
          if (e instanceof Throwable) {
            reSbf.append(LogUtil.getExceptionString((Throwable) e)).append("\n");
          } else {
            reSbf.append(LogUtil.getStackTraceString(e)).append("\n");
          }
          reSbf.append("]***");
        }
        return reSbf.toString();
    }
	
	
	
	/**
	 * 处理交易数据日志内容
	 * 刘虻
	 * 2009-12-4 上午11:30:07
	 * @param content 未处理的日志内容
	 * @return        处理后的日志内容
	 */
	public String fixDataContent(Object content) {
    //构建返回值
    return new StringBuffer() 
      .append("\n[dat] t:[")
      .append(shadow.now())
      .append("] b:[")
      .append(getBossName())
      .append("] h:[")
      .append(Thread.currentThread().getId())
      .append("] s:[")
      .append(SString.valueOf(ThreadSession.get(IServletConst.KEY_SESSION_ID)))
      .append("] p:[")
      .append(SString.valueOf(ThreadSession.get(IServletConst.KEY_ACTION_IP)))
      .append("] a:[")
      .append(SString.valueOf(ThreadSession.get(IServletConst.KEY_ACTION_NAME)))
      .append("]\n***c:[")
      .append(content)
      .append("]***")
      .toString();
  }
	
	/**
	 * 获取调用类名
	 * 刘虻
	 * 2009-12-4 下午04:43:56
	 * @return 调用类名
	 */
	protected String getBossName() {
		if(logBossClassName==null) {
			return "";
		}
		return logBossClassName;
	}
	
	/**
	 * 设置调用类实例
	 * 刘虻
	 * 2010-2-1 下午03:37:18
	 * @param boss 调用类实例
	 */
	public ILog setBoss(Object boss) {
		if(boss==null) {
			return this;
		}
		//构造返回值
		logBossClassName = boss.getClass().getName();
		if(logBossClassName!=null) {
			//分隔符
			int point = logBossClassName.lastIndexOf(".");
			if(point>0) {
				logBossClassName = logBossClassName.substring(point+1);
			}
		}
		return this;
	}
	
	/**
	 * 设置调用类
	 * @param boss 调用类
	 * @return 调用类
	 * 2018年9月13日
	 * @author MBG
	 */
	@SuppressWarnings("rawtypes")
	public ILog setBoss(Class boss) {
		if(boss==null) {
			return this;
		}
		//构造返回值
		logBossClassName = boss.getName();
		if(logBossClassName!=null) {
			//分隔符
			int point = logBossClassName.lastIndexOf(".");
			if(point>0) {
				logBossClassName = logBossClassName.substring(point+1);
			}
		}
		return this;
	}
	
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2009-12-4 下午05:01:48
	 */
	@Override
  public void error(Object content, Object e) {
    if(SBoolean.valueOf(ThreadSession.get("_nolog_"))) {
        return;
    }
    //设置故障码
    if(ThreadSession.containsKey("_action_")) {
      shadow.setAlertCode(LogAlertCode.NORMAL_ERROR_FRONT);
    }else {
      shadow.setAlertCode(LogAlertCode.NORMAL_ERROR_BACK);
    }
    shadow
      .write(
          fixContent("err",null,content,e,false)
          ,shadow.isOutError()
          ,shadow.isWriteError()
          ,false);
	}
	
	
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2009-12-4 下午05:01:48
	 */
	@Override
    public String getConsoleEncoding() {
		return shadow.getConsoleEncoding();
	}
	
	/**
	 * 覆盖方法
	 * 2020年8月20日
	 * @author MBG
	 */
	@Override
	public String getFileEncoding() {
		return shadow.getFileEncoding();
	}

	/**
	 * 覆盖方法
	 * 刘虻
	 * 2009-12-4 下午05:01:48
	 */
	@Override
    public void log(Object content) {
        if(SBoolean.valueOf(ThreadSession.get("_nolog_"))) {
            return;
        }
        shadow
			.write(
					fixContent("log",null,content,null,false)
					,shadow.isOutLog()
					,shadow.isWriteLog()
					,false);
	}

	/**
	 * 覆盖方法
	 * 刘虻
	 * 2009-12-4 下午05:01:48
	 */
	@Override
    public void log(String title,Object content) {
        if(SBoolean.valueOf(ThreadSession.get("_nolog_"))) {
            return;
        }
        shadow
			.write(
					fixContent("log",title,content,null,false)
					,shadow.isOutLog()
					,shadow.isWriteLog()
					,false);
	}

	/**
	 * 写入自定义日志
	 * 需要在影子类中设置允许输出的文件头信息，否则日志不会输出
	 * 另外，自定义文件头名字不允许使用  log  或者以 data_ 开头的名字
	 * @param content  日志内容
	 * @param fileKey  自定义日志文件头
	 * 2018年7月30日
	 * @author MBG
	 */
	@Override
  @SuppressWarnings("rawtypes")
	public void olog(Object content,String fileKey) {
		//输出内容
		StringBuffer outSbf = new StringBuffer();
		outSbf
      .append("t:[")
      .append(shadow.now())
      .append("] c:");
    if(content instanceof Exception) {
      outSbf.append("\n").append(DebugUtil.getExceptionInfo((Exception)content)).append("\n");
    }else if(content instanceof Map) {
      outSbf.append("Map:\n").append(DebugUtil.getMapValue((Map)content)).append("\n");
    }else if(content instanceof List) {
      outSbf.append("List:\n").append(DebugUtil.getListValue((List)content)).append("\n");
    }else {
      outSbf.append("\n").append(content).append("\n");
    }
    //写入日志
    shadow.write(outSbf.toString(),shadow.isOutLog(),fileKey);
	}

	/**
	 * 写入自定义日志
	 * 需要在影子类中设置允许输出的文件头信息，否则日志不会输出
	 * 另外，自定义文件头名字不允许使用  log  或者以 data_ 开头的名字
   * @param title    日志提示标题
	 * @param content  日志内容
	 * @param fileKey  自定义日志文件头
	 * 2018年7月30日
	 * @author MBG
	 */
	@Override
  @SuppressWarnings("rawtypes")
	public void olog(String title,Object content,String fileKey) {
    if(title==null){
      title = "";
    }else{
      title += ":";
    }
		//输出内容
		StringBuffer outSbf = new StringBuffer();
		outSbf
      .append("t:[")
      .append(shadow.now())
      .append("] c:");
    if(content instanceof Exception) {
      outSbf.append(title).append("\n").append(DebugUtil.getExceptionInfo((Exception)content)).append("\n");
    }else if(content instanceof Map) {
      outSbf.append(title).append("Map:\n").append(DebugUtil.getMapValue((Map)content)).append("\n");
    }else if(content instanceof List) {
      outSbf.append(title).append("List:\n").append(DebugUtil.getListValue((List)content)).append("\n");
    }else {
      outSbf.append(title).append("\n").append(content).append("\n");
    }
    //写入日志
    shadow.write(outSbf.toString(),shadow.isOutLog(),fileKey);
	}

	/**
	 * 覆盖方法
	 * 刘虻
	 * 2009-12-4 下午05:01:48
	 */
	@Override
    public Object runBefore() {
		return new ExecuteTimeVO();
	}

	/**
	 * 覆盖方法
	 * 刘虻
	 * 2009-12-4 下午05:01:48
	 */
	@Override
    public void sqlLog(Object sql) {
        if(SBoolean.valueOf(ThreadSession.get("_nolog_"))) {
            return;
        }
        shadow
			.write(
					fixContent("sql",null,sql,null,false)
					,shadow.isOutSql()
					,shadow.isWriteSql()
					,false);
	}
	
	/**
	 * 输出sql语句日志
	 * @param sql       sql语句
	 * @param outSql    是否输出日志到控制台
	 * @param writeSql  是否将日志写入到文件
	 * 2019年3月6日
	 * @author MBG
	 */
	@Override
    public void sqlLog(Object sql, boolean outSql, boolean writeSql) {
        if(SBoolean.valueOf(ThreadSession.get("_nolog_"))) {
            return;
        }
        shadow
			.write(
					fixContent("sql",null,sql,null,false)
					,outSql && shadow.isOutSql()
					,writeSql && shadow.isWriteSql()
					,false);
	}
	
	
	/**
	 * 输出sql语句日志
	 * @param sql       sql语句
	 * @param outSql    是否输出日志到控制台
	 * @param fileKey   写入文件的文件名（不带扩展名）
	 * 2019年3月6日
	 * @author MBG
	 */
	@Override
    public void sqlLog(Object sql, boolean outSql, String fileKey) {
        if(SBoolean.valueOf(ThreadSession.get("_nolog_"))) {
            return;
        }
        shadow
			.write(
					fixContent("sql",null,sql,null,false)
					,outSql && shadow.isOutSql()
					,fileKey);
	}

	/**
	 * 覆盖方法
	 * 刘虻
	 * 2009-12-4 下午05:01:48
	 */
	@Override
    public void startLog(Object content) {
	    shadow
			.writeStart(
					fixContent("start",null,content==null?"":content.toString(),null,false)
					,shadow.isOutStart()
					,shadow.isWriteStart());
	}
	
	
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2009-12-4 下午05:01:48
	 */
	@Override
    public void warning(Object content, Object e) {
        if(SBoolean.valueOf(ThreadSession.get("_nolog_warning_"))) {
            return;
        }
        //设置故障码
        if(ThreadSession.containsKey("_action_")) {
        	shadow.setAlertCode(LogAlertCode.NORMAL_WARNING_FRONT);
        }else {
        	shadow.setAlertCode(LogAlertCode.NORMAL_WARNING_BACK);
        }
        shadow
			.write(
					fixContent("wrn",null,content,e,false)
					,shadow.isOutWarning()
					,shadow.isWriteWarning()
					,false);
	}
	
  /**
   * 将所有日志屏蔽后，还需要在控制台显示一些量不大，又关键的日志信息
   * @param content 日志内容
   * 2014年6月17日
   * @author 马宝刚
   */
  @Override
  public void info(Object content) {
      if(SBoolean.valueOf(ThreadSession.get("_nolog_"))) {
          return;
      }
      shadow
        .write(
          fixContent("ifo",null,content,null,false)
          ,shadow.isOutInfo()
          ,shadow.isWriteInfo()
          ,false);
  }

  /**
   * 将所有日志屏蔽后，还需要在控制台显示一些量不大，又关键的日志信息
   * @param content 日志内容
   * 2014年6月17日
   * @author 马宝刚
   */
  @Override
  public void info(String title,Object content) {
      if(SBoolean.valueOf(ThreadSession.get("_nolog_"))) {
          return;
      }
      shadow
        .write(
          fixContent("ifo",title,content,null,false)
          ,shadow.isOutInfo()
          ,shadow.isWriteInfo()
          ,false);
  }

	/**
	 * 覆盖方法
	 * 刘虻
	 * 2009-12-4 下午05:01:48
	 */
	@Override
  public void writeRuntime(Object executeTimeVo, String title) {
    if(SBoolean.valueOf(ThreadSession.get("_nolog_"))) {
        return;
    }
    //输出信息
    String info = 
      (new StringBuffer())
        .append("\n[rut] t:[")
        .append(shadow.now())
        .append("] b:[")
        .append(getBossName())
        .append("] h:[")
        .append(Thread.currentThread().getId())
        .append("] s:[")
        .append(SString.valueOf(ThreadSession.get(IServletConst.KEY_SESSION_ID)))
        .append("] p:[")
        .append(SString.valueOf(ThreadSession.get(IServletConst.KEY_ACTION_IP)))
        .append("] a:[")
        .append(SString.valueOf(ThreadSession.get(IServletConst.KEY_ACTION_NAME)))
        .append("] ms:[")
        .append(((ExecuteTimeVO)executeTimeVo).getRunTimeInfo())
        .append("]\n***c:[")
        .append(title)
        .append("]***")
        .toString();
    shadow
      .write(
          info
          ,shadow.isOutRuntime()
          ,shadow.isWriteRuntime()
          ,false);
	}

	/**
	 * 覆盖方法
	 * 刘虻
	 * 2009-12-7 上午10:34:04
	 */
	@Override
  @SuppressWarnings("unchecked")
	public <T> T shadowInstance() {
    //构建返回值
    return (T)new XLog(shadow);
	}

	/**
	 * 设置影子类
	 * 刘虻
	 * 2009-12-7 上午10:34:58
	 * @param shadow 影子类
	 */
	public void setShadow(ILogShadow shadow) {
		this.shadow = shadow;
	}
	
	/**
	 * 获取影子类
	 * 刘虻
	 * 2009-12-7 上午10:35:08
	 * @return 影子类
	 */
	public ILogShadow getShadow() {
        return shadow;
	}

	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-9-10 下午04:50:46
	 */
	@Override
    public void destroy() {
		if(shadow==null) {
			return;
		}
		shadow.destroy();
	}
	
  /**
   * 输出内部日志，该日志不会输出到控制台，也不会写入文件 
   * 只会在设置了日志事件处理类，将日志信息输出到到日志事件 处理类中
   * @param content 日志内容 
   * 2014年4月17日
   * @author 马宝刚
   */
  @Override
  public void debug(Object content) {
    if (SBoolean.valueOf(ThreadSession.get("_nolog_"))) {
      return;
    }
    shadow.writeLogEvent(fixContent("deb", null, content, null, false));
    if (shadow.isWriteDebug()) {
      shadow.write(fixContent("deb", null, content, null, false), false, true, false);
    }
  }

  /**
   * 输出内部日志，该日志不会输出到控制台，也不会写入文件 
   * 只会在设置了日志事件处理类，将日志信息输出到到日志事件 处理类中
   * @param title   日志提示标题
   * @param content 日志内容 
   * 2014年4月17日
   * @author 马宝刚
   */
  @Override
  public void debug(String title,Object content) {
    if (SBoolean.valueOf(ThreadSession.get("_nolog_"))) {
      return;
    }
    shadow.writeLogEvent(fixContent("deb", title, content, null, false));
    if (shadow.isWriteDebug()) {
      shadow.write(fixContent("deb", title, content, null, false), false, true, false);
    }
  }

  /**
    * 输出交易数据日志
    * @param content 交易数据
    * 2016年6月7日
    * @author MBG
    */
	@Override
  public void dataLog(Object content) {
    shadow
    .writeData(
        fixDataContent(content)
        ,shadow.isOutData()
        ,shadow.isWriteData());
	}
	
	/**
	 * 输出临时日志（测试后需要删除的日志） 
	 * @param content 日志内容
	 */
	@Override
  public void tlog(Object content) {
    shadow
      .writeData(
          fixContent("tmp",null,content,null,true)
          ,shadow.isOutTLog()
        ,shadow.isWriteTLog());
	}

	/**
	 * 输出临时日志（测试后需要删除的日志）
   * @param title   日志提示标题
	 * @param content 日志内容
	 */
	@Override
  public void tlog(String title,Object content) {
    shadow
      .writeData(
          fixContent("tmp",title,content,null,true)
          ,shadow.isOutTLog()
        ,shadow.isWriteTLog());
	}
	
	/**
	 * 设置故障码
	 * @param code   故障码
	 * 2018年7月17日
	 * @author MBG
	 */
	@Override
    public void setAlertCode(String code) {
		shadow.setAlertCode(code);
	}
}
